package com.itcoon.cloud.framework.webmvc.trace;

import com.itcoon.cloud.framework.utils.IPUtil;
import com.itcoon.cloud.framework.webmvc.config.properties.GlobalWebConfigProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

@WebFilter
public class HttpTraceLogFilter extends OncePerRequestFilter implements Ordered {

    private static final Logger log = LoggerFactory.getLogger(HttpTraceLogFilter.class);

    private static final AntPathMatcher ANT_PATH_MATCHER = new AntPathMatcher();

    private final List<GlobalWebConfigProperties.HttpTrace> httpTraces;

    public HttpTraceLogFilter(GlobalWebConfigProperties properties) {
        this.httpTraces = Arrays.stream(properties.getApis()).map(GlobalWebConfigProperties.Api::getHttpTrace).collect(Collectors.toList());
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE + 100;
    }

    @Override
    public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        if (!isRequestValid(request)) {
            filterChain.doFilter(request, response);
            return;
        }
        if (!(request instanceof ContentCachingRequestWrapper)) {
            request = new ContentCachingRequestWrapper(request);
        }
        if (!(response instanceof ContentCachingResponseWrapper)) {
            response = new ContentCachingResponseWrapper(response);
        }
        HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
        long startTime = System.currentTimeMillis();

        String contentType = request.getContentType();
        if (StringUtils.isEmpty(contentType)) {
            contentType = request.getHeader("contentType");
        }
        MediaType mediaType = contentType == null ? MediaType.ALL : MediaType.valueOf(contentType);
        if (mediaType.includes(MediaType.MULTIPART_FORM_DATA) || mediaType.includes(MediaType.APPLICATION_FORM_URLENCODED)) {
            request.getParameterMap();
        }
        RequestWrapper requestWrapper = null;
        try {
            if (mediaType.includes(MediaType.APPLICATION_JSON)) {
                requestWrapper = new RequestWrapper(request);
                filterChain.doFilter(requestWrapper, response);
            } else {
                filterChain.doFilter(request, response);
            }
            status = HttpStatus.valueOf(response.getStatus());
        } finally {
            //过滤器
            String path = request.getRequestURI();
            Optional<GlobalWebConfigProperties.HttpTrace> traceOp = httpTraces.stream().filter(httpTrace -> path.startsWith(httpTrace.getBasic())).findFirst();
            if(traceOp.isPresent()){
                GlobalWebConfigProperties.HttpTrace httpTrace = traceOp.get();
                List<String> excludeUris = httpTrace.getExcludeUris();
                List<MediaType> ignoreContentTypes = httpTrace.getIgnoreContentTypes();
                boolean flag = excludeUris.stream().noneMatch(uri -> ANT_PATH_MATCHER.match(uri, path));
                flag = flag && ignoreContentTypes.stream().noneMatch(ignoreContentType -> ignoreContentType.includes(mediaType));
                // level级别控制
                HttpTraceLog traceLog = generateLog(flag, status, mediaType, requestWrapper, request,
                        response, path, startTime);
                // 日志处理
                HttpTraceLogger httpTraceLogger = new HttpTraceLogger(httpTrace);
                httpTraceLogger.resolve(traceLog);
            }
            updateResponse(response);
        }
    }

    private HttpTraceLog generateLog(boolean flag, HttpStatus status, MediaType mediaType, RequestWrapper requestWrapper, HttpServletRequest request, HttpServletResponse response, String uri, long startTime) {
        HttpTraceLog traceLog = new HttpTraceLog();
        traceLog.setPath(uri);
        traceLog.setMethod(request.getMethod());
        traceLog.setTimeTaken(System.currentTimeMillis() - startTime);
        traceLog.setStatus(status.value());
        if (flag) {
            if (mediaType.includes(MediaType.APPLICATION_JSON)) {
                if (requestWrapper != null) {
                    String requestBody = requestWrapper.getRequestBody();
                    if (requestBody.length() < 500) {
                        traceLog.setRequestBody(requestBody);
                    }
                }
            }
            traceLog.setParameterMap(request.getParameterMap());
            MediaType responseContentType = response.getContentType() == null ? MediaType.ALL : MediaType.valueOf(response.getContentType());
            if (responseContentType.includes(MediaType.APPLICATION_JSON)) {
                String responseBody = getResponseBody(response);
                if (responseBody.length() < 500) {
                    traceLog.setResponseBody(responseBody);
                }

            }
            traceLog.setRequestHeaderMap(getRequestHeaderMap(request));
            traceLog.setResponseHeaderMap(getResponseHeaderMap(response));
            traceLog.setUserIp(IPUtil.getUserIP(request));
        }
        return traceLog;
    }

    private boolean isRequestValid(HttpServletRequest request) {
        try {
            new URI(request.getRequestURL().toString());
            return true;
        } catch (URISyntaxException ex) {
            return false;
        }
    }

    private Map<String, String> getRequestHeaderMap(HttpServletRequest request) {
        Map<String, String> headerMap = new HashMap<>();
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            String headerValue = request.getHeader(headerName);
            headerMap.put(headerName, headerValue);
        }
        return headerMap;
    }

    private Map<String, String> getResponseHeaderMap(HttpServletResponse response) {
        Map<String, String> headerMap = new HashMap<>();
        Collection<String> headerNames = response.getHeaderNames();
        for (String headerName : headerNames) {
            String headerValue = response.getHeader(headerName);
            headerMap.put(headerName, headerValue);
        }
        return headerMap;
    }

    private String getResponseBody(HttpServletResponse response) {
        String responseBody = "";
        ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
        if (wrapper != null) {
            try {
                responseBody = StreamUtils.copyToString(wrapper.getContentInputStream(), StandardCharsets.UTF_8);
            } catch (IOException e) {
                log.error(e.getMessage(), e);
            }
        }
        return responseBody;
    }

    private void updateResponse(HttpServletResponse response) throws IOException {
        ContentCachingResponseWrapper responseWrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
        Objects.requireNonNull(responseWrapper).copyBodyToResponse();
    }

}
